﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NetFrameworkExtensions.ComponentModel;
using System.Diagnostics;
using NetFrameworkExtensions.BusinessLayer.Properties;
using System.Linq.Expressions;

namespace NetFrameworkExtensions.BusinessLayer
{
  [Serializable]
  public abstract class IdentifiedBase<TIdentified, TIdentifier> :
    NotifyObject,
    IIdentified<TIdentified, TIdentifier>,
    IPropertyContainer,
    IFreezable,
    IFreezableInternal,
    IFormatted,
    IConvertible
    where TIdentified : IdentifiedBase<TIdentified, TIdentifier>
    where TIdentifier : IEquatable<TIdentifier>
  {
    private TIdentifier _identifier;
    private bool _isFrozen;

    protected IdentifiedBase()
    {
      Initialize();
    }

    protected IdentifiedBase(IIdentified<TIdentifier> other)
    {
      if (other == null) throw new ArgumentNullException("other");
      Identifier = other.Identifier;
      ((IPropertyContainer)this).SetProperties(other);
      Initialize();
    }

    private void Initialize()
    {
      RegisterQuickFiring(() => Formatted);
    }

    #region [ IIdentified ]
    public TIdentifier Identifier
    {
      get { return _identifier; }
      set
      {
        if (_identifier.Equals(value)) return;
        CheckFrozen();
        _identifier = value;
        OnPropertyChanged(() => Identifier);
      }
    }
    object IIdentified.Identifier
    {
      get
      {
        return this.Identifier;
      }
      set
      {
        this.Identifier = (TIdentifier)value;
      }
    }

    public Type IdentifiedType
    {
      get { return typeof(TIdentified); }
    }

    public Type IdentifierType
    {
      get { return typeof(TIdentifier); }
    }
    #endregion

    #region [ IFreezable ]
    public bool IsFrozen
    {
      get { return _isFrozen; }
      set
      {
        if (value == _isFrozen) return;
        CheckFrozen();
        Debug.Assert(!_isFrozen);
        Debug.Assert(value);
        if (!OnBeforeFreeze()) throw new InvalidOperationException(FormattedResources.ExCannotFreezeObjectAtThisTime);
        FreezeInternal();
        OnFrozen();
        OnPropertyChanged(() => IsFrozen);
      }
    }
    #endregion

    #region [ IFreezableInternal ]
    protected void CheckFrozen()
    {
      if (_isFrozen)
        throw new InvalidOperationException(Resources.ExFrozenObject);
    }

    protected virtual bool OnBeforeFreeze()
    {
      return true;
    }

    protected virtual void FreezeInternal()
    {
    }

    protected virtual void OnFrozen()
    {
    }

    bool IFreezableInternal.OnBeforeFreeze()
    {
      var childProps = this.GetType().GetProperties().Where(p => p.CanRead && p.GetIndexParameters().Length == 0 && p.GetCustomAttributes(typeof(IsContentPropertyAttribute), true).Any()).ToArray();
      foreach (var prop in childProps)
      {
        var child = prop.GetValue(this, null) as IFreezableInternal;
        if (child != null && !child.OnBeforeFreeze()) 
          return false;
      }
      return this.OnBeforeFreeze();
    }

    void IFreezableInternal.FreezeInternal()
    {
      var childProps = this.GetType().GetProperties().Where(p => p.CanRead && p.GetIndexParameters().Length == 0 && p.GetCustomAttributes(typeof(IsContentPropertyAttribute), true).Any()).ToArray();
      foreach (var prop in childProps)
      {
        var child = prop.GetValue(this, null) as IFreezableInternal;
        if (child != null)
          child.FreezeInternal();
      }
      this.FreezeInternal();
      _isFrozen = true;
    }

    void IFreezableInternal.OnFrozen()
    {
      var childProps = this.GetType().GetProperties().Where(p => p.CanRead && p.GetIndexParameters().Length == 0 && p.GetCustomAttributes(typeof(IsContentPropertyAttribute), true).Any()).ToArray();
      foreach (var prop in childProps)
      {
        var child = prop.GetValue(this, null) as IFreezableInternal;
        if (child != null)
          child.OnFrozen();
      }
      this.OnFrozen();
    }
    #endregion

    #region [ IPropertyContainer ]
    protected void AssignProperty<T>(Expression<Func<T>> property, ref T variable, T value, bool condition = true)
    {
      if (condition)
      {
        variable = value;
        OnPropertyChanged(property);
      }
    }

    protected virtual void SetPropertiesStart(IIdentified item)
    {
      SetProperties(item);
    }
    protected abstract void SetProperties(IIdentified item);

    void IPropertyContainer.SetProperties(IIdentified item)
    {
      if (item != null)
        SetPropertiesStart(item);
    }
    #endregion

    #region [ IFormatted ]
    public abstract string Formatted { get; }
    #endregion

    #region [ IConvertible ]
    TypeCode IConvertible.GetTypeCode()
    {
      throw new NotImplementedException();
    }

    bool IConvertible.ToBoolean(IFormatProvider provider)
    {
      throw new NotImplementedException();
    }

    byte IConvertible.ToByte(IFormatProvider provider)
    {
      throw new NotImplementedException();
    }

    char IConvertible.ToChar(IFormatProvider provider)
    {
      throw new NotImplementedException();
    }

    DateTime IConvertible.ToDateTime(IFormatProvider provider)
    {
      throw new NotImplementedException();
    }

    decimal IConvertible.ToDecimal(IFormatProvider provider)
    {
      throw new NotImplementedException();
    }

    double IConvertible.ToDouble(IFormatProvider provider)
    {
      throw new NotImplementedException();
    }

    short IConvertible.ToInt16(IFormatProvider provider)
    {
      throw new NotImplementedException();
    }

    int IConvertible.ToInt32(IFormatProvider provider)
    {
      throw new NotImplementedException();
    }

    long IConvertible.ToInt64(IFormatProvider provider)
    {
      throw new NotImplementedException();
    }

    sbyte IConvertible.ToSByte(IFormatProvider provider)
    {
      throw new NotImplementedException();
    }

    float IConvertible.ToSingle(IFormatProvider provider)
    {
      throw new NotImplementedException();
    }

    string IConvertible.ToString(IFormatProvider provider)
    {
      return Formatted;
    }

    object IConvertible.ToType(Type conversionType, IFormatProvider provider)
    {
      if (conversionType.IsAssignableFrom(this.GetType()))
        return this;
      if (conversionType == typeof(string))
        return ((IConvertible)this).ToString();
      throw new NotImplementedException();
    }

    ushort IConvertible.ToUInt16(IFormatProvider provider)
    {
      throw new NotImplementedException();
    }

    uint IConvertible.ToUInt32(IFormatProvider provider)
    {
      throw new NotImplementedException();
    }

    ulong IConvertible.ToUInt64(IFormatProvider provider)
    {
      throw new NotImplementedException();
    }
    #endregion

    #region [ Object ]
    public override string ToString()
    {
      return Formatted;
    }

    public override bool Equals(object obj)
    {
      var other = obj as IIdentified<TIdentifier>;
      if (other == null) return false;
      return GetType() == obj.GetType() &&
        Identifier.Equals(other.Identifier);
    }

    public override int GetHashCode()
    {
      return GetType().GetHashCode() ^ Identifier.GetHashCode();
    }
    #endregion

  }

  [Serializable]
  public abstract class ConcurrentIdentifiedBase<TIdentified, TIdentifier> :
    IdentifiedBase<TIdentified, TIdentifier>,
    IConcurrent
    where TIdentified : ConcurrentIdentifiedBase<TIdentified, TIdentifier>
    where TIdentifier : IEquatable<TIdentifier>
  {
    private byte[] _timestamp;
    public ConcurrentIdentifiedBase()
    {
    }

    public ConcurrentIdentifiedBase(IIdentified<TIdentifier> other)
      : base(other)
    {
    }

    #region [ IConcurrent ]
    public byte[] Timestamp
    {
      get { return _timestamp; }
      set
      {
        if (_timestamp.AreEqualsArray(value)) return;
        CheckFrozen();
        _timestamp = value;
        OnPropertyChanged(() => Timestamp);
      }
    }
    #endregion

    #region [ IPropertyContainer ]
    protected override void SetPropertiesStart(IIdentified item)
    {
      AssignProperty(() => Timestamp, ref _timestamp, ((IConcurrent)item).Timestamp == null ? null : (byte[])((IConcurrent)item).Timestamp.Clone(), !Timestamp.AreEqualsArray(((IConcurrent)item).Timestamp));
      base.SetPropertiesStart(item);
    }
    #endregion

    #region [ Object ]
    public override bool Equals(object obj)
    {
      var other = obj as IConcurrent;
      if (other == null) return false;
      return base.Equals(obj) &&
        Timestamp.AreEqualsArray(other.Timestamp);
    }

    public override int GetHashCode()
    {
      return base.GetHashCode();
    }
    #endregion

  }



}
